<?php

namespace Ktnw\CurdSupport\Utils;

use App\Constants\ParamType;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use ReflectionClass;

class BeanUtils
{


    /**
     * 拷贝相同属性
     * @param $fromObj object 源
     * @param $toObj object 目标
     * @param  $exclude array 需要排除的属性
     * @param $isEmptySet bool 空值是否设置 true-设置; false-不设置; 默认为true.
     * @throws Exception
     */
    public static function beanCopyProps($fromObj, &$toObj, array $exclude = [], bool $isEmptySet = false)
    {
        if (!isset($fromObj) || !isset($toObj)) {
            throw new Exception("copyProps 错误");
        }

        if (!is_object($fromObj) || !is_object($toObj)) {
            throw new Exception("BeanUtils.copyProps 传参错误");
        }

        $refToClass = new ReflectionClass($toObj);
        $toProps    = $refToClass->getProperties();
        if (!$toProps) {
            return;
        }

        $refFromClass = new ReflectionClass($fromObj);
        $fromProps    = $refFromClass->getProperties();
        if (!$fromProps) {
            return;
        }
        foreach ($toProps as $k => $prop) {
            $propName = $prop->getName();
            // 转为下划线命名
            $snakePropName = Str::snake($propName);
            // 转为驼峰命名
            $camelPropName = Str::camel($propName);
            if (!empty($exclude) && in_array([$snakePropName, $camelPropName], $exclude)) {
                // 若是需要排除的属性  则跳过本次循环
                continue;
            }

            if (!$prop->isPublic()) {
                $prop->setAccessible(true);
            }
            foreach ($fromProps as $key => $p) {
                if (!$p->isPublic()) {
                    $p->setAccessible(true);
                }
                if ($p->getName() == $propName || $p->getName() == $snakePropName || $p->getName() == $camelPropName) {
                    $value = $p->getValue($fromObj);
                    if (!empty($value) || $isEmptySet) {
                        $prop->setValue($toObj, $value);
                    }
                    break;
                }
            }
        }
    }

    /**
     * 给$model设置属性
     * @param $fromObj object 源
     * @param $model Model 目标
     * @param $isEmptySet bool 空值是否设置 true-设置; false-不设置; 默认为true.
     * @param $exclude array 需要排除的属性
     * @throws Exception
     */
    public static function copyProps($fromObj, Model &$model, array $exclude = [], bool $isEmptySet = false)
    {
        if (!isset($fromObj) || !isset($model)) {
            throw new Exception("copyPropsToModel 错误");
        }

        if (!is_object($fromObj) || !is_object($model)) {
            throw new Exception("BeanUtils.copyPropsToModel 传参错误");
        }

        $refFromClass = new ReflectionClass($fromObj);
        $fromProps    = $refFromClass->getProperties();
        if (!$fromProps) {
            return;
        }

        // 获取表结构字段
        $columns = $model->getTableColumns();
        if (empty($columns)) {
            return;
        }

        foreach ($fromProps as $prop) {
            // 驼峰命名转下划线
            $propName = Str::snake($prop->getName());
            if (!empty($exclude) && in_array($propName, $exclude)) {
                // 若是需要排除的属性  则跳过本次循环
                continue;
            }
            if (!in_array($propName, $columns)) {
                // 若不为数据库表中字段  则跳过本次循环
                continue;
            }
            if (!$prop->isPublic()) {
                $prop->setAccessible(true);
            }
            $value = $prop->getValue($fromObj);
            if (!empty($value) || $isEmptySet) {
                $model->$propName = $value === "" ? null : $value;
            }
        }

    }

    /**
     * 数组copy到对象
     * @param array $arr
     * @param object $target
     * @param array $exclude
     * @param bool $isEmptySet
     * @throws Exception
     */
    public static function arrayToBean(array $arr, &$target, array $exclude = [], bool $isEmptySet = false)
    {
        if (empty($arr)) {
            return;
        }

        if (!is_object($target)) {
            throw new Exception("BeanUtils.arrayToBean 传参错误");
        }

        $targetClass = new ReflectionClass($target);
        $props       = $targetClass->getProperties();
        if (!$props) {
            return;
        }

        foreach ($props as $prop) {
            $propName = $prop->getName();
            // 转为下划线命名
            $snakePropName = Str::snake($propName);
            // 转为驼峰命名
            $camelPropName = Str::camel($propName);
            if (!empty($exclude) && in_array([$snakePropName, $camelPropName], $exclude)) {
                // 若是需要排除的属性  则跳过本次循环
                continue;
            }
            if (!$prop->isPublic()) {
                $prop->setAccessible(true);
            }
            if (array_key_exists($snakePropName, $arr)) {
                $prop->setValue($target, $arr[$snakePropName]);
            }
            if (array_key_exists($camelPropName, $arr)) {
                $prop->setValue($target, $arr[$camelPropName]);
            }
        }

    }

    /**
     * 数组拷贝
     * @param array $fromArray
     * @param array $toArray
     * @param bool $isEmptySet 空值是否设置 true-设置; false-不设置;
     * @return array
     */
    public static function copyArray(array $fromArray, array $toArray, bool $isEmptySet = false): array
    {
        if (empty($fromArray)) {
            return $toArray;
        }
        if (empty($toArray)) {
            return $fromArray;
        }

        foreach ($fromArray as $key => $value) {
            if (!empty($value) || $isEmptySet) {
                $toArray[$key] = $value;
            }
        }

        return $toArray;
    }

    /**
     * 类转换为数组
     * @param object $bean 实例类
     * @param bool $isEmptySet 空值是否设置 true-设置; false-不设置;
     * @param string $paramType 转换后key的链接方式 camel-驼峰式；snake-下划线式；
     * @return array
     */
    public static function beanToArray($bean, bool $isEmptySet = false, string $paramType = "camel"): array
    {
        $refClass = new ReflectionClass($bean);
        $props    = $refClass->getProperties();
        if (!$props) {
            return [];
        }

        $array = [];
        foreach ($props as $prop) {
            if (!$prop->isPublic()) {
                $prop->setAccessible(true);
            }
            $propName = $prop->getName();
            if ($paramType == "camel") {
                $propName = Str::camel($propName);
            }
            if ($paramType == "snake") {
                $propName = Str::snake($propName);
            }
            $value = $prop->getValue($bean);
            if (is_bool($value) || is_integer($value)) {
                $array[$propName] = $value;
            } else if (!empty($value) || $isEmptySet) {
                $array[$propName] = $value === "" ? null : $value;
            }
        }
        return $array;
    }

}